package parser;

import java.text.ParseException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import metadata.schema.Schema;

import parser.absyn.*;
import represent.Scan;
import represent.TablePlan;
import represent.expression.*;
import represent.type.*;
import server.SimpleDB;
import storage.transaction.Transaction;

/**
 * A fetcher to retrieve data from absyn
 * @author Jermaine Wang
 */

public class FetchData {

	public FetchData() {
	}
		
	//----------------------------------------------------
	// Query Data scanner
	//----------------------------------------------------	
	public QueryData scanQuery(Query q, Transaction tx) {
		Select s = (Select) q;
		return scanSelect(s, tx);
	}
	
	private QueryData scanSelect(Select s, Transaction tx) {
		/** Members for parsing query */
		/** A list of all table names */
		List<Table> tables = new LinkedList<Table>();
		// Scan from clause, get all used tables
		TableRefList tp = s.tbl_ref_list;
		while(tp != null) {
			if(tp.subquery == null)
				tables.add(new NameTable(tp.tbl_name, tp.alias));
			else {
				QueryData subquery = scanQuery(tp.subquery, tx);
				tables.add(new QueryTable(subquery, tp.alias));
			}
			tp = tp.next;
		}
		// Scan select expressions & set alias to each expression.
		List<Expression> exprs = new LinkedList<Expression>();
		SelectExpList sp = s.select_expr_list;
		while(sp != null) {
			SelectExp se = sp.select_expr;
			if(se.value != null) {
				Expression e = scanValue(se.value, tx);
				// Set alias to expressions.
				e.setAlias(se.alias);
				exprs.add(e);
			}
			else { // * operator: do not add any expressions, 
				   // leave this to preprocess.
				
			}
			sp = sp.next;
		}
		// Scan where clause
		Predicate where_cond = scanBoolExp(s.where_condition, tx);
		// Scan Group by
		String group_fld = (s.group_col_name == null)? null : scanColName(s.group_col_name, tx);
		List<String> groupfields = new LinkedList<String>();
		if(group_fld != null) {
			groupfields = new LinkedList<String>();
			groupfields.add(group_fld);
		}
		// Scan having clause
		Predicate having_cond = scanBoolExp(s.having_condition, tx);
		// Scan Order by
		List<String> order_fld = new LinkedList<String>();
		List<Integer> order_dir = new LinkedList<Integer>();
		OrderList o = s.orders;
		while(o != null) {
			String fname = scanColName(o.col_name, tx);
			order_fld.add(fname);
			if(o.order == OrderList.ASC)
				order_dir.add(QueryData.ASC);
			else
				order_dir.add(QueryData.DESC);
			o = o.next;
		}
		
		// Create the query data
		QueryData qd = new QueryData(exprs, s.distinct, tables, where_cond, 
				groupfields, having_cond, order_fld, order_dir);
		qd.makeSchema(tx);
		return qd;
	}
	
	//----------------------------------------------------
	// Manipulate Data scanner
	//----------------------------------------------------
	public ManipulateData scanMan(Manipulation m, Transaction tx) {
		if(m instanceof CreateDB)
			return scanCreatDB((CreateDB) m, tx);
		else if(m instanceof CreateIndex)
			return scanCreateIndex((CreateIndex) m, tx);
		else if(m instanceof CreateTable)
			return scanCreateTable((CreateTable) m, tx);
		else if(m instanceof DropDB)
			return scanDropDB((DropDB) m, tx);
		else if(m instanceof DropIndex)
			return scanDropIndex((DropIndex) m, tx);
		else if(m instanceof DropTable)
			return scanDropTable((DropTable) m, tx);
		else if(m instanceof Insert)
			return scanInsert((Insert) m, tx);
		else if(m instanceof Delete)
			return scanDelete((Delete) m, tx);
		else if(m instanceof Update)
			return scanUpdate((Update) m, tx);
		else if(m instanceof UseDB)
			return scanUse((UseDB) m, tx);
		else throw new Error("fatworm.parser.FetchData");
	}

	private UseDBData scanUse(UseDB m, Transaction tx) {
		return new UseDBData(m.db_name);
	}

	private ModifyData scanUpdate(Update m, Transaction tx) {
		List<String> fieldNames = new LinkedList<String>();
		List<Expression> newVals = new LinkedList<Expression>();
		SetList p = m.set_exprs;
		while(p != null) {
			fieldNames.add(scanColName(p.col_name, tx));
			newVals.add(scanValue(p.value, tx));
			p = p.next;
		}
		Predicate pred = scanBoolExp(m.where_condition, tx);
		return new ModifyData(m.table_name, fieldNames, newVals, pred);
	}

	private DeleteData scanDelete(Delete m, Transaction tx) {
		Predicate pred = scanBoolExp(m.where_condition, tx);
		return new DeleteData(m.table_name, pred);
	}
	
	private InsertData scanInsert(Insert m, Transaction tx) {
		List<String> flds = new LinkedList<String>();
//		TimeTool.getTimer(PrimaryTest.getTableInfoTimer).setStart();
		Schema sch = SimpleDB.mdMgr().getTableInfo(m.table_name, tx).schema();
//		System.out.println(sch);
//		TimeTool.getTimer(PrimaryTest.getTableInfoTimer).setEnd();
		if(m.fields == null)
			flds = sch.fields();
		else
			flds = m.fields;
		if(m.subquery == null) {
			Iterator<String> f_iter = flds.iterator();
			List<Type> vals = new LinkedList<Type>();
			ValueList vl = m.values;
			while(vl != null) {
				String fldname = f_iter.next();
				Type c = scanValue(vl.value, tx).asConstant();
				if(c instanceof DefaultType) {
					if(sch.isAutoIncre(fldname)) {
						TablePlan tp = new TablePlan(new NameTable(m.table_name, null), tx);
						Scan s = tp.open(null);
						Type best = null;
						int count = 0;
						while(s.next()) {count ++;
							Type tmp = s.getVal(fldname);
							if(best == null || tmp.compareTo(best) > 0)
								best = tmp;
						}
						if(best == null)
							c = sch.defaultValue(fldname);
						else
							c = best.plus(new IntType(1));
					}
					else
						c = sch.defaultValue(fldname);
//					System.out.println("Get default " + c);
				}
				vals.add(c);
				vl = vl.next;
			}
			if(flds.size() == 0)
				flds = null;
			return new InsertData(m.table_name, flds, vals);
		}
		else {// subquery
			QueryData sub = scanQuery(m.subquery, tx);
			sub.preprocess(tx);
			if(flds.size() == 0)
				flds = null;
			return new InsertData(m.table_name, flds, sub);
		}
	}

	private DropTableData scanDropTable(DropTable m, Transaction tx) {
		List<String> tblNames = new LinkedList<String>();
		DropNameList names = m.names;
		while(names != null) {
			tblNames.add(names.name);
			names = names.next;
		}
		return new DropTableData(tblNames);
	}

	private DropIndexData scanDropIndex(DropIndex m, Transaction tx) {
		return new DropIndexData(m.index_name, m.table_name);
	}

	private DropDBData scanDropDB(DropDB m, Transaction tx) {
		return new DropDBData(m.db_name);
	}

	private CreateTableData scanCreateTable(CreateTable m, Transaction tx) {
		Schema schema = scanCreateTableDef(m.definitions, tx);
		return new CreateTableData(m.table_name, schema);
	}
	
	private Schema scanCreateTableDef(CreateDefList l, Transaction tx) {
		Schema schema = new Schema();
		CreateDefList p = l;
		while(p != null) {
			if(p.def instanceof ColumnDef) {
				ColumnDef cd = (ColumnDef) (p.def);
				String fldname = scanColName(cd.col_name, tx);
				ConstantExpression de_val = new ConstantExpression(new NullType());
				if(cd.auto_incre) {
					if(cd.data_type.equals("int"))
						de_val = new ConstantExpression(new IntType(1));
					else if(cd.data_type.equals("float"))
						de_val = new ConstantExpression(new FloatType(1.0));
				}
				if(cd.default_var != null)
					de_val = scanValueConst(cd.default_var, tx);
				schema.addField(fldname, cd.data_type, cd.isNull, de_val.asConstant(), cd.auto_incre);
			}
			else if(p.def instanceof PrimaryKey) {
				PrimaryKey pd = (PrimaryKey) (p.def);
				String fldname = scanColName(pd.col_name, tx);
				schema.setPrimaryKey(fldname);
			}
			else throw new Error("fatorm.parser.FetchData.scanCreateTableDef");
			p = p.next;
		}
		return schema;
	}

	private CreateIndexData scanCreateIndex(CreateIndex m, Transaction tx) {
		String t = m.table_name;
		String i = m.index_name;
		boolean u = m.isUnique;
		String f = scanColName(m.col_name, tx);
		return new CreateIndexData(i, t, f, u);
	}

	private CreateDBData scanCreatDB(CreateDB m, Transaction tx) {
		return new CreateDBData(m.db_name);
	}
	
	//----------------------------------------------------
	// Value scanner
	//----------------------------------------------------
	private Expression scanValue(Value v, Transaction tx) {
		if(v instanceof ValueCol)
			return scanColNameExp((ValueCol) v, tx);
		else if(v instanceof ValueConst)
			return scanValueConst((ValueConst) v, tx);
		else if(v instanceof ValueFunc)
			return scanValueFunc((ValueFunc) v, tx);
		else if(v instanceof ValueOp)
			return scanValueOp((ValueOp) v, tx);
		else if(v instanceof ValueQuery)
			return scanValueQuery((ValueQuery)v, tx);
		else throw new Error("fatworm.parser.FetchData.scanValue");
	}
	
	private Expression scanValueQuery(ValueQuery v, Transaction tx) {
		QueryData qd = scanQuery(v.subquery, tx);
		return new QueryExpression(qd, tx);
	}

	private Expression scanValueOp(ValueOp v, Transaction tx) {
		Expression left = scanValue(v.left, tx);
		Expression right = scanValue(v.right, tx);
		return new OpExpression(left, right, v.aop);
	}

	private Expression scanValueFunc(ValueFunc v, Transaction tx) {
		return new FnExpression(scanColName(v.col, tx), v.func);
	}

	private ConstantExpression scanValueConst(ValueConst v, Transaction tx) {
		if(v instanceof ValueInt)
			return scanValueInt((ValueInt) v, tx);
		else if(v instanceof ValueBool)
			return scanValueBool((ValueBool) v, tx);
		else if(v instanceof ValueString)
			return scanValueString((ValueString) v, tx);	//also date value
		else if(v instanceof ValueDec)
			return scanValueFloat((ValueDec) v, tx);
		else if(v instanceof ValueNull)
			return new ConstantExpression(new NullType());
		else if(v instanceof ValueDefault)
			return new ConstantExpression(new DefaultType());
		else throw new Error("fatworm.parser.FetchData.scanValueConst");
	}

	private ConstantExpression scanValueFloat(ValueDec v, Transaction tx) {
		return new ConstantExpression(new DecimalType(v.value));
	}

	private ConstantExpression scanValueString(ValueString v, Transaction tx) {
		return new ConstantExpression(new StringType(v.value));
	}

	private ConstantExpression scanValueBool(ValueBool v, Transaction tx) {
		return new ConstantExpression(new BoolType(v.value));
	}

	private ConstantExpression scanValueInt(ValueInt v, Transaction tx) {
		return new ConstantExpression(new IntType(v.value));
	}

	private Expression scanColNameExp(ValueCol colName, Transaction tx) {
		return new FieldNameExpression(scanColName(colName, tx));
	}
	
	private String scanColName(ValueCol colName, Transaction tx) {
		String tmp = "";
		if(colName.tbl_name != null)
			tmp += colName.tbl_name + ".";
		tmp += colName.col_name;
		return tmp;
	}
	
	//----------------------------------------------------
	// Bool Exp scanner
	//----------------------------------------------------
	private Predicate scanBoolExp(BoolExp b, Transaction tx) {
		if(b instanceof BoolOpExp)
			return scanBoolOpExp((BoolOpExp) b, tx);
		else if(b instanceof BoolSetExp)
			return scanBoolSetExp((BoolSetExp) b, tx);
		else if(b instanceof BoolValueExp)
			return scanBoolValueExp((BoolValueExp) b, tx);
		else if(b == null)
			return new Predicate();
		else throw new Error("fatworm.parser.FetchData.scanBoolExp");
	}

	private Predicate scanBoolValueExp(BoolValueExp b, Transaction tx) {
		Expression lhs = scanValue(b.left, tx);
		Expression rhs = scanValue(b.right, tx);
		return new Predicate(new OpTerm(lhs, rhs, b.cop));
	}

	private Predicate scanBoolSetExp(BoolSetExp b, Transaction tx) {
		Expression val = null;
		if(b.value != null)
			val = scanValue(b.value, tx);
		QueryData qd = scanQuery(b.subquery, tx);
		return new Predicate(new QueryTerm(qd, val, b.set_op, b.cop, tx));
	}

	private Predicate scanBoolOpExp(BoolOpExp b, Transaction tx) {
		Predicate lhs = scanBoolExp(b.left, tx);
		Predicate rhs = scanBoolExp(b.right, tx);
		lhs.conjoinWith(rhs, b.op);
		return lhs;
	}
	
	/**
	 * A name pool to give col names.
	 * @author Jermaine
	 */
	class ColNamePool {
		private int id = 0;
		public ColNamePool() {}
		public String getColName() {
			return "c" + (id++);
		}
	}
}
